package pl;

import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.Util;
import javacard.security.CryptoException;
import javacard.security.Key;
import javacard.security.KeyBuilder;
import javacard.security.MessageDigest;
import javacard.security.RSAPrivateKey;
import javacard.security.RSAPublicKey;
import javacardx.crypto.Cipher;

public class MediatorEngine {

	private static final short SW_CARD_BLOCKED = (short) (ISO7816.SW_CONDITIONS_NOT_SATISFIED + 2);
	private static final short SW_NO_MESSAGE_TO_SIGN = (short) (ISO7816.SW_WRONG_DATA + 1);
	private static final short SW_INVALID_CONTAINER = (short) (ISO7816.SW_DATA_INVALID - 2);
	/*
	 * byte[] hashSHA1 = new byte[] { (byte) 0x37, (byte) 0xB6, (byte) 0x6A,
	 * (byte) 0xE0, (byte) 0x44, (byte) 0x58, (byte) 0x43, (byte) 0x35, (byte)
	 * 0x3D, (byte) 0x47, (byte) 0xEC, (byte) 0xB0, (byte) 0xB4, (byte) 0xFD,
	 * (byte) 0x14, (byte) 0xC1, (byte) 0x10, (byte) 0xE6, (byte) 0x2D, (byte)
	 * 0x6A };
	 */

	final static short SW_CRYPTO_EXCEPTION = (short) 0x68A3;
	final static short SW_SIGNATURE_GENERATION_ERROR = (short) 0x68A5;

	final static short SW_CONTAINER_NOT_SET = (short) 0x68A7;

	final static byte SIG_RESP_TAG = (byte) 0x30;
	final static byte PRESIG_TAG = (byte) 0x80;
	final static byte PADD_TAG = (byte) 0x81;
	final static short MAX_RSA_LEN = (short) 2048;
	final static short MAX_EMLEN = (short) (MAX_RSA_LEN / 8);

	/* mechanizmy szyfrowania */
	final static short CKA_RSA_PKCS = (short) 0;
	final static short CKA_RSA_PSS = (short) 1;

	// Signature rsaPss;
	/* keys */

	private Cipher cipherRSA;
	private EMSA_PSS pss;
	private Cipher cipherRSA2;

	// private byte keyIdentificator;
	private MessageDigest messDig;

	private short saltLen;
	// private boolean finalized;

	byte[] currentHMAC;

	private byte[] preSignature;
	short presignatureLen;

	public byte[] result;
	public short resLen;
	byte[] hash;

	byte keyId;
	boolean isCompleted;
	short hashLen;

	byte[] certificate;
	short certLen;

	boolean hasMessageToEncrypt;

	private boolean isKeyEvoEngineEnable;

	MessageDigest hmacInst;

	PrimitiveBERTLV signatureTlv;
	PrimitiveBERTLV randomTlv;
	PrimitiveBERTLV hmacTlv;
	ConstructedBERTLV responseTlv;
	PrimitiveBERTLV decryptedTlv;

	PrimitiveBERTag tagRandom;
	PrimitiveBERTag tagPresignature;
	PrimitiveBERTag tagHmac;
	PrimitiveBERTag tagDecrypted;

	ConstructedBERTag cTag;
	KeyEvolutionEngine keyEvoEng;

	PrimitiveBERTLV paddingTlv;
	PrimitiveBERTag tagPadding;

	private short messageOff;
	private byte[] message;
	private short messageLen;

	private byte[] padding;
	private short paddingLen;
	// private short decodedLen;
	// private boolean isKeyEvoEng;

	short publicExpLen;
	short hmacLen;
	short hmacOffset;
	short presigOffset;
	short presigLen;

	KeyContainer[] keys;

	byte currentKid = -1;

	byte mechanism = CKA_RSA_PKCS;

	public MediatorEngine() {

		// keyIdentificator = 1;
		saltLen = (short) 20;

		messDig = MessageDigest.getInstance(MessageDigest.ALG_SHA, false); // out
		hashLen = messDig.getLength();

		// hash result array
		hash = JCSystem.makeTransientByteArray(hashLen,
				JCSystem.CLEAR_ON_DESELECT);

		result = new byte[(short) 512];// JCSystem.makeTransientByteArray((short)512,
		// JCSystem.CLEAR_ON_DESELECT);

		preSignature = new byte[MAX_EMLEN];

		padding = new byte[128];

		/*
		 * privateKeyExponent = JCSystem.makeTransientByteArray( (short)
		 * (MAX_RSA_LEN / 8), JCSystem.CLEAR_ON_DESELECT);
		 */

		// privateKeyExponent = new byte[(short) (MAX_RSA_LEN / 8)];

		/*
		 * modulus = JCSystem.makeTransientByteArray((short) (MAX_RSA_LEN / 8),
		 * JCSystem.CLEAR_ON_DESELECT);
		 */

		// modulus = new byte[(short) (MAX_RSA_LEN / 8)];

		// keySeed = new byte[(short) 256];

		// publicKeyExponent = new byte[(short) 8];

		pss = new EMSA_PSS();

		keyEvoEng = new KeyEvolutionEngine();

		certificate = new byte[(short) 1024];

		currentHMAC = new byte[(short) 64];

		cipherRSA = Cipher.getInstance(Cipher.ALG_RSA_NOPAD, false);
		cipherRSA2 = Cipher.getInstance(Cipher.ALG_RSA_PKCS1, false);

		hmacInst = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false);

		message = new byte[(short) 512];

		messageOff = 0;

		initTags();

		/*
		 * n1 = new Bignat(); n2 = new Bignat(); mod = new Bignat(); mulResult =
		 * new Bignat();
		 */

		isKeyEvoEngineEnable = true;

		keys = new KeyContainer[3];

		keys[0] = new KeyContainer();
		keys[0].setKeyId((byte) 0x00);

		keys[1] = new KeyContainer();
		keys[1].setKeyId((byte) 0x02);

		keys[2] = new KeyContainer();
		keys[2].setKeyId((byte) 0x03);
	}

	private void initTags() {

		responseTlv = new ConstructedBERTLV((short) 4);
		signatureTlv = new PrimitiveBERTLV((short) 256);
		randomTlv = new PrimitiveBERTLV((short) 256);
		hmacTlv = new PrimitiveBERTLV((short) 256);
		paddingTlv = new PrimitiveBERTLV((short) 256);

		decryptedTlv = new PrimitiveBERTLV((short) 256);

		tagPadding = (PrimitiveBERTag) BERTag.getInstance(
				new byte[] { (byte) 0x80 }, (short) 0);
		tagPresignature = (PrimitiveBERTag) BERTag.getInstance(
				new byte[] { (byte) 0x81 }, (short) 0);
		tagRandom = (PrimitiveBERTag) BERTag.getInstance(
				new byte[] { (byte) 0x82 }, (short) 0);
		tagHmac = (PrimitiveBERTag) BERTag.getInstance(
				new byte[] { (byte) 0x83 }, (short) 0);

		tagDecrypted = (PrimitiveBERTag) BERTag.getInstance(
				new byte[] { (byte) 0x84 }, (short) 0);

		cTag = (ConstructedBERTag) BERTag.getInstance(
				new byte[] { (byte) 0x30 }, (short) 0);
	}

	public void selectKeyContainer(byte kid) {

		if (!keyEvoEng.finalized)
			ISOException.throwIt(SW_CARD_BLOCKED);

		if (kid > keys.length)
			ISOException.throwIt(SW_INVALID_CONTAINER);

		// 0 -> 1

		if (currentKid > -1) {
			if (keyEvoEng.isInitialized)
				keys[currentKid].setKeySeed(keyEvoEng.nextKey);
		}

		keyId = kid;

		if (keys[keyId].isCompleted) {

			currentKid = keyId;

			short modLen = (short) (keys[keyId].keySize);

			pss.init(saltLen, modLen);

			if (!keyEvoEng.isInitialized) {

				keyEvoEng.init(hmacInst, keys[keyId].keySeed,
						keys[keyId].keySeedLength);

				keys[keyId].isPssInitaliazed = true;

				keyEvoEng.isInitialized = true;

			} else {

				Util.arrayCopy(keys[keyId].keySeed, (short) 0,
						keyEvoEng.nextKey, (short) 0, keys[keyId].keySeedLength);

				Util.arrayCopy(keys[keyId].keySeed, (short) 0, keyEvoEng.key,
						(short) 0, keys[keyId].keySeedLength);
			}

		} else
			ISOException.throwIt(SW_INVALID_CONTAINER);
	}

	public void initializeKeys(byte[] array, short offset, short len) {

		currentKid = -1;

		byte kid = findKid(array, offset, len);

		keys[kid].initalize(array, offset, len);

		// ISOException.throwIt((short) (0x6300 + currentKid));
	}

	public void setEngineParameters(byte[] inputBuffer, byte offsetCdata,
			short incomingLength) {

		// tag algorithm
		if (inputBuffer[offsetCdata] == (byte) 0x80) {

			offsetCdata++;
			// check algorithm value
			if (inputBuffer[offsetCdata] == (byte) 0x01) {

				offsetCdata++;
				if (inputBuffer[offsetCdata] == 0x01) {
					// set algorithm
					offsetCdata++;
					// tag key current key id
					if (inputBuffer[offsetCdata] == 0x81) {

					}
				}
			}
		}
	}

	public void changeKeys() {
		isCompleted = false;
		/* TODO: */};

	/* methods for presignature generation */

	private void encrypt(byte[] plain, short inOffset, short inLength,
			byte[] encrypted, short outOffset) {

		try {
			if (keys[currentKid].getPrivateKey() != null
					&& keys[currentKid].publicKey != null) {

				cipherRSA.init(keys[currentKid].getPrivateKey(),
						Cipher.MODE_ENCRYPT);
				cipherRSA.doFinal(plain, inOffset, inLength, encrypted,
						outOffset);

				if (keys[currentKid].isContainerUsed == false)
					keys[currentKid].isContainerUsed = true;
			} else
				ISOException.throwIt(ISO7816.SW_WRONG_DATA); // Invalid key
		} catch (CryptoException cex) {

			ISOException.throwIt(SW_CRYPTO_EXCEPTION);
		}
	}

	private void decrypt(byte[] encrypted, short encOffset, short encLength,
			byte[] plain, short outOffset, Key key, byte cipherMode) {
		try {
			if (key != null) {
				cipherRSA.init(key, cipherMode);
				cipherRSA.doFinal(encrypted, encOffset, encLength, plain,
						outOffset);
			} else
				ISOException.throwIt(ISO7816.SW_WRONG_DATA); // Invalid key
		} catch (CryptoException cex) {

			ISOException.throwIt(SW_CRYPTO_EXCEPTION);
		}
	}

	private void decrypt(byte[] encrypted, short encOffset, short encLength,
			byte[] plain, short outOffset) {
		try {
			/*if (keys[currentKid].privateKey != null
					&& keys[currentKid].publicKey != null) {*/
			if (keys[currentKid].getPrivateKey() != null
					&& keys[currentKid].publicKey != null) {
				cipherRSA.init(keys[currentKid].publicKey, Cipher.MODE_DECRYPT);

				cipherRSA.doFinal(encrypted, encOffset, encLength, plain,
						outOffset);

				if (keys[currentKid].isContainerUsed == false)
					keys[currentKid].isContainerUsed = true;

			} else
				ISOException.throwIt(ISO7816.SW_WRONG_DATA); // Invalid key
		} catch (CryptoException cex) {

			ISOException.throwIt(SW_CRYPTO_EXCEPTION);
		}

		/*
		 * try { if (privateKey != null && publicKey != null) {
		 * cipherRSA.init(publicKey, Cipher.MODE_DECRYPT);
		 * cipherRSA.doFinal(encrypted, encOffset, encLength, plain, outOffset);
		 * } else ISOException.throwIt(ISO7816.SW_WRONG_DATA); // Invalid key }
		 * catch (CryptoException cex) {
		 * 
		 * ISOException.throwIt(SW_CRYPTO_EXCEPTION); }
		 */
	}

	public void hashInIcc(byte[] message, byte offsetCdata, short lc) {

		if (keyEvoEng.finalized == true) {

			if ((byte) message[(short) (1 + offsetCdata)] == 0x40) {

				hasMessageToEncrypt = false;
				messDig.update(message, (short) (2 + offsetCdata),
						(short) (lc - 2));

				Util.arrayCopy(message, (short) (2 + offsetCdata),
						this.message, this.messageOff, (short) (lc - 2));

				this.messageOff += (short) (lc - 2);

			} else {

				ISOException.throwIt((short) (ISO7816.SW_WRONG_LENGTH));

				/*
				 * messDig.doFinal(message, (short) (2 + offsetCdata), (short)
				 * (lc - 2), hash, (short) 0); messDig.reset();
				 * 
				 * Util.arrayCopy(message, (short) (2 + offsetCdata),
				 * this.message, this.messageOff, (short) (lc - 2));
				 * hasMessageToEncrypt = true;
				 * 
				 * this.messageLen = (short) (this.messageOff + (short) (lc -
				 * 2));
				 * 
				 * this.messageOff = 0;
				 */

			}
		} else
			ISOException.throwIt(SW_CARD_BLOCKED);

		/*
		 * if (keyEvoEng.finalized == true) { if ((byte) message[(short) (1 +
		 * offsetCdata)] == 0x40) {
		 * 
		 * hasMessageToEncrypt = false; messDig.update(message, (short) (2 +
		 * offsetCdata), (short) (lc - 2));
		 * 
		 * Util.arrayCopy(message, (short) (2 + offsetCdata), this.message,
		 * this.messageOff, (short) (lc - 2));
		 * 
		 * this.messageOff += (short) (lc - 2);
		 * 
		 * } else {
		 * 
		 * messDig.doFinal(message, (short) (2 + offsetCdata), (short) (lc - 2),
		 * hash, (short) 0); messDig.reset();
		 * 
		 * Util.arrayCopy(message, (short) (2 + offsetCdata), this.message,
		 * this.messageOff, (short) (lc - 2)); hasMessageToEncrypt = true;
		 * 
		 * this.messageLen = (short) (this.messageOff + (short) (lc - 2));
		 * 
		 * this.messageOff = 0;
		 * 
		 * } } else ISOException.throwIt((short)
		 * (ISO7816.SW_CONDITIONS_NOT_SATISFIED + 2));
		 */
	}

	public void hashInIccFinal(byte[] message, byte offsetCdata, short lc) {
		if (keyEvoEng.finalized == true) {

			if ((byte) message[(short) (1 + offsetCdata)] < 0x41) {

				messDig.doFinal(message, (short) (2 + offsetCdata),
						(short) (lc - 2), hash, (short) 0);

				messDig.reset();

				Util.arrayCopy(message, (short) (2 + offsetCdata),
						this.message, this.messageOff, (short) (lc - 2));

				hasMessageToEncrypt = true;

				this.messageLen = (short) (this.messageOff + (short) (lc - 2));

				this.messageOff = 0;
			} else {
				ISOException.throwIt((short) (ISO7816.SW_WRONG_LENGTH));
			}
		} else {
			ISOException.throwIt(SW_CARD_BLOCKED);
		}
	}

	/*
	 * public short hashInIcc(byte[] message, byte offsetCdata, short lc, byte[]
	 * ouput, short outOff) {
	 * 
	 * if (keyEvoEng.finalized == true) { if ((byte) message[(short) (1 +
	 * offsetCdata)] == 0x40) {
	 * 
	 * hasMessageToEncrypt = false; messDig.update(message, (short) (2 +
	 * offsetCdata), (short) (lc - 2));
	 * 
	 * Util.arrayCopy(message, (short) (2 + offsetCdata), this.message,
	 * this.messageOff, (short) (lc - 2));
	 * 
	 * this.messageOff += (short) (lc - 2);
	 * 
	 * return 0;
	 * 
	 * } else {
	 * 
	 * messDig.doFinal(message, (short) (2 + offsetCdata), (short) (lc - 2),
	 * hash, (short) 0);
	 * 
	 * Util.arrayCopy(hash, (short) 0, ouput, outOff, this.hashLen);
	 * 
	 * messDig.reset();
	 * 
	 * Util.arrayCopy(message, (short) (2 + offsetCdata), this.message,
	 * this.messageOff, (short) (lc - 2));
	 * 
	 * hasMessageToEncrypt = true;
	 * 
	 * this.messageLen = (short) (this.messageOff + (short) (lc - 2));
	 * 
	 * this.messageOff = 0;
	 * 
	 * return hashLen;
	 * 
	 * } } else ISOException .throwIt(SW_CARD_BLOCKED);
	 * 
	 * return -1; }
	 */

	public void sign() {

		if (hasMessageToEncrypt) {

			if (keyEvoEng.finalized == false) {
				unblock();
			} else {

				pss.emsaPssEncode(hash);

				paddingTlv.init(this.tagPadding, pss.result, (short) 0,
						pss.emLen);

				encrypt(pss.result, (short) 0, pss.emLen, preSignature,
						(short) 0);

				signatureTlv.init(tagPresignature, this.preSignature,
						(short) 0, pss.emLen);

				// Build tlv response
				// 1. signature
				responseTlv.init(cTag, signatureTlv);

				// 2. padding
				responseTlv.append(paddingTlv);

				// responseTlv.append(signatureTlv);

				keyEvoEng.PerformKeyEvo(preSignature, (short) 0, pss.emLen);

				// 3. random - append to tlv HMAC
				randomTlv.init(tagRandom, keyEvoEng.getRandom(), (short) 0,
						keyEvoEng.rLen);

				responseTlv.append(randomTlv);

				// always commit, just for tests
				// keyEvoEng.commitEvolutionStep();

				keyEvoEng.createHmacWithNewKey(preSignature, (short) 0,
						pss.emLen, currentHMAC, (short) 0);

				// 3. HMAC append to tlv HMAC
				hmacTlv.init(this.tagHmac, currentHMAC, (short) 0,
						keyEvoEng.hmacLen);

				responseTlv.append(hmacTlv);

				resLen = responseTlv.size();
				responseTlv.toBytes(result, (short) 0);
			}

		} else
			ISOException.throwIt(SW_NO_MESSAGE_TO_SIGN);
	}

	public void sign(byte[] messageToSign, short messageOff, short messageLen) {

		if (true) {

			if (keyEvoEng.finalized == false) {
				unblock();
			} else {

				short paddingLen = 0x00;

				switch (mechanism) {
				
				case CKA_RSA_PKCS:

					// message przechowuje wiadomosc do weryfikacji
					Util.arrayCopy(messageToSign, messageOff, this.message,
							(short) 0, messageLen);

					/* padding PKCS 1.5 -> nie hash tylko sama wiadomosc */
					paddingLen = encodePkcs1(messageToSign, messageOff,
							messageLen, this.padding);
					this.messageLen = messageLen;
					
					
					paddingTlv.init(this.tagPadding,  this.padding, (short) 0,
							paddingLen);
					
					encrypt(this.padding, (short) 0, paddingLen, preSignature,
							(short) 0);
					
					signatureTlv.init(tagPresignature, this.preSignature,
							(short) 0, paddingLen);
					
					break;
				case CKA_RSA_PSS:

					// hash message
					messDig.doFinal(messageToSign, messageOff, messageLen,
							hash, (short) 0);

					messDig.reset();

					// message przechowuje wiadomosc do weryfikacji
					Util.arrayCopy(messageToSign, messageOff, this.message,
							(short) 0, messageLen);	
					this.messageLen = messageLen;
					
					/* padding PSS */
					pss.emsaPssEncode(hash);
					
					paddingTlv.init(this.tagPadding, pss.result, (short) 0,
							pss.emLen);

					encrypt(pss.result, (short) 0, pss.emLen, preSignature,
							(short) 0);

					signatureTlv.init(tagPresignature, this.preSignature,
							(short) 0, pss.emLen);

					break;
				default:
					ISOException.throwIt(SW_CRYPTO_EXCEPTION);
				}

				/* Build tlv response */

				// 1. signature
				responseTlv.init(cTag, signatureTlv);

				// 2. padding
				responseTlv.append(paddingTlv);

				// responseTlv.append(signatureTlv);

				keyEvoEng.PerformKeyEvo(preSignature, (short) 0, pss.emLen);

				// 3. random - append to tlv HMAC
				randomTlv.init(tagRandom, keyEvoEng.getRandom(), (short) 0,
						keyEvoEng.rLen);

				responseTlv.append(randomTlv);

				// always commit, just for tests
				// keyEvoEng.commitEvolutionStep();

				keyEvoEng.commitEvolutionStep();
				hasMessageToEncrypt = false;
				
				keyEvoEng.createHmacWithNewKey(preSignature, (short) 0,
						pss.emLen, currentHMAC, (short) 0);

				// 3. HMAC append to tlv HMAC
				hmacTlv.init(this.tagHmac, currentHMAC, (short) 0,
						keyEvoEng.hmacLen);

				responseTlv.append(hmacTlv);

				resLen = responseTlv.size();

			/*	Util.arrayCopy(messageToSign, messageOff, this.message,
						(short) 0, messageLen);
				this.messageLen = messageLen;*/

				responseTlv.toBytes(result, (short) 0);
			}

		} else
			ISOException.throwIt(SW_NO_MESSAGE_TO_SIGN);
	}

	public void unblock() {

		if (isKeyEvoEngineEnable) {

			signatureTlv.init(tagPresignature, this.preSignature, (short) 0,
					pss.emLen);

			// Build tlv response
			// 1. signature
			responseTlv.init(cTag, signatureTlv);

			// 2. padding
			responseTlv.append(paddingTlv);

			// keyEvoEng.PerformKeyEvo(preSignature, (short) 0, pss.emLen);

			// 3. random - append to tlv HMAC
			randomTlv.init(tagRandom, keyEvoEng.getRandom(), (short) 0,
					keyEvoEng.rLen);

			responseTlv.append(randomTlv);

			keyEvoEng.createHmacWithNewKey(preSignature, (short) 0, pss.emLen,
					currentHMAC, (short) 0);

			// 3. HMAC append to tlv HMAC
			hmacTlv.init(this.tagHmac, currentHMAC, (short) 0,
					keyEvoEng.hmacLen);

			responseTlv.append(hmacTlv);

			// convert response to byte stre
			resLen = responseTlv.size();

			responseTlv.toBytes(result, (short) 0);

		} else
			ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
	}

	public void verify(byte[] dataForVerify, short dOff, short len) {

		decrypt(dataForVerify, dOff, len, result, (short) 0);

		switch (mechanism) {
		case CKA_RSA_PKCS:
			//if (!pkcsVerify(message, messageLen, result))
			if (!decodePkcs1(result, (short) 0, len, message, (short) 0, this.messageLen)) 
				ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
			else {
				keyEvoEng.commitEvolutionStep();
				hasMessageToEncrypt = false;
				ISOException.throwIt(ISO7816.SW_NO_ERROR);
			}
			break;
		case CKA_RSA_PSS:
			if (!pss.emsaPssVerify(message, messageLen, result))
				ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
			else {
				keyEvoEng.commitEvolutionStep();
				hasMessageToEncrypt = false;
				ISOException.throwIt(ISO7816.SW_NO_ERROR);
			}
			break;
		default:
			ISOException.throwIt(SW_CRYPTO_EXCEPTION);
		}

	}

	public short decryptWithUserKey(byte[] encryptedMessage, short offset,
			short eLen) {
		/*
		 * if (eLen != modulusLength) ISOException
		 * .throwIt(ISO7816.SW_DATA_INVALID);
		 */

		// Utils.ClearArray(result);

		// decrypt(encryptedMessage, offset, eLen, result, (short) 0);

		decrypt(encryptedMessage, offset, eLen, result, (short) 0,
				keys[currentKid].getPrivateKey(), Cipher.MODE_DECRYPT);

		decryptedTlv.init(this.tagDecrypted, result, (short) 0, eLen);

		responseTlv.init(cTag, decryptedTlv);

		// perform key evolution on encrypted message with user key
		keyEvoEng.PerformKeyEvo(encryptedMessage, offset, eLen);

		// build response tlv -> decryptedData, random, hmac
		randomTlv.init(tagRandom, keyEvoEng.getRandom(), (short) 0,
				keyEvoEng.rLen);

		responseTlv.append(randomTlv);

		// always commit, just for tests
		keyEvoEng.commitEvolutionStep();

		keyEvoEng.createHmacWithNewKey(encryptedMessage, offset, eLen,
				currentHMAC, (short) 0);

		// 3. HMAC append to tlv HMAC
		hmacTlv.init(this.tagHmac, currentHMAC, (short) 0, keyEvoEng.hmacLen);

		responseTlv.append(hmacTlv);

		resLen = responseTlv.size();
		responseTlv.toBytes(result, (short) 0);

		return responseTlv.size();

		// return -> random, hmac
		/*
		 * Byte[] decryptedData = (new BigInteger(1,
		 * partialDecryptedDataByFinalizator).Multiply( new BigInteger(1,
		 * partialDecryptedDataByUser))).Mod(publicKey.Modulus).ToByteArray();
		 */
	}

	public byte[] getPreSignature() {
		return preSignature;
	}

	public short getPublicKeyModulus(byte[] outputBuffer, short outOff) {

		if (currentKid < 0)
			ISOException.throwIt(SW_CONTAINER_NOT_SET);

		if (keys[currentKid].publicKey == null
				|| !keys[currentKid].publicKey.isInitialized()) {
			ISOException.throwIt((short) (0x6300 + currentKid));

			return 0;
		}

		short modulusLen = keys[currentKid].publicKey.getModulus(outputBuffer,
				outOff);

		return modulusLen;
	}

	public short getPublicKeyExponent(byte[] outputBuffer, short outOff) {

		if (currentKid < 0)
			ISOException.throwIt(SW_CONTAINER_NOT_SET);

		if (keys[currentKid].publicKey == null
				|| !keys[currentKid].publicKey.isInitialized()) {
			ISOException.throwIt((short) (0x6300 + currentKid));
			return 0;
		}

		short publicExpoLen = keys[currentKid].publicKey.getExponent(
				outputBuffer, outOff);

		return publicExpoLen;
	}

	public short getEvolutionKey(byte[] outputBuffer, short outOff) {

		if (currentKid < 0)
			ISOException.throwIt(SW_CONTAINER_NOT_SET);

		/*
		 * if (keys[currentKid].keySeedLength == 0) {
		 * ISOException.throwIt((short) (0x6300 + currentKid)); return 0; }
		 */

		Util.arrayCopy(keyEvoEng.key, (short) 0, outputBuffer, outOff,
				keyEvoEng.kLen);

		/*
		 * Util.arrayCopy(keys[currentKid].keySeed, (short) 0, outputBuffer,
		 * outOff, keys[currentKid].keySeedLength);
		 */
		return keyEvoEng.kLen;
	}

	public short getUserCertificate(byte[] outputBuffer, short outOff) {

		if (currentKid < 0)
			ISOException.throwIt(SW_CONTAINER_NOT_SET);

		Util.arrayCopy(keys[currentKid].userCertificate, (short) 0,
				outputBuffer, outOff, keys[currentKid].userCertLen);

		return keys[currentKid].userCertLen;

		// SW_CONTAINER_NOT_SET
	}

	public void setUserCertificate(byte[] inputBuffer, short inOffset,
			short inLen) {

		keys[keyId].setUserCertificate(inputBuffer, inOffset, inLen);

	}

	public void setCertificateId(byte[] inputBuffer, short inOffset, short inLen) {

		//
		// currentKid = keyId;
		keys[currentKid].setCertificateId(inputBuffer, inOffset, inLen);

	}

	public short getUserCertificateId(byte[] outputBuffer, short outOff) {

		if (currentKid < 0)
			ISOException.throwIt(SW_CONTAINER_NOT_SET);

		Util.arrayCopy(keys[currentKid].certificateId, (short) 0, outputBuffer,
				outOff, keys[currentKid].certificateIdLen);

		return keys[currentKid].certificateIdLen;

	}

	public short getCertificateLabel(byte[] outputBuffer, short outOff) {

		if (currentKid < 0)
			ISOException.throwIt(SW_CONTAINER_NOT_SET);

		Util.arrayCopy(keys[currentKid].certificateLabel, (short) 0,
				outputBuffer, outOff, keys[currentKid].certificateLabelLen);

		return keys[currentKid].certificateLabelLen;

	}

	public void setCertificateLabel(byte[] inputBuffer, short inOffset,
			short inLen) {
		// TODO Auto-generated method stub
		keys[currentKid].setCertificateLabel(inputBuffer, inOffset, inLen);
	}

	/*
	 * private void initalize(byte[] inputArray, short offset, short len) {
	 * 
	 * // short size = 0; if (inputArray[offset] == (byte) 0xA8) { offset++;
	 * offset += Utils.getSizeBytesLen(inputArray, offset);
	 * 
	 * // key id 0xA8 0x4A 0xB6 0x03 0x84 0x01 0x01 0x7F 0x48 0x42 0x97 // 0x40
	 * 0x2D 0x0A 0xA0 0x19 0xBD 0xB2 0xE3
	 * 
	 * if ((byte) inputArray[offset] == (byte) 0xB6) { offset++; offset +=
	 * Utils.getSizeBytesLen(inputArray, offset);
	 * 
	 * // key id value tag if ((byte) inputArray[offset] == (byte) 0x84) {
	 * offset++; // L offset += Utils.getSizeBytesLen(inputArray, offset); // V
	 * 
	 * keyId = inputArray[offset];
	 * 
	 * offset++;
	 * 
	 * if ((byte) inputArray[offset] == (byte) 0x7F && (byte) inputArray[(short)
	 * (offset + 1)] == (byte) 0x48) {
	 * 
	 * offset += 2; // L offset += Utils.getSizeBytesLen(inputArray, offset); //
	 * V
	 * 
	 * switch ((byte) inputArray[offset]) {
	 * 
	 * case (byte) 0x97:
	 * 
	 * offset++; privateKeyExponentLength = Utils.getLength( inputArray,
	 * offset); offset += Utils.getSizeBytesLen(inputArray, offset); // V
	 * 
	 * Util.arrayCopy(inputArray, offset, privateKeyExponent, (short) 0,
	 * privateKeyExponentLength);
	 * 
	 * break;
	 * 
	 * case (byte) 0x98:
	 * 
	 * offset++;
	 * 
	 * keySeedLength = Utils.getLength(inputArray, offset);
	 * 
	 * offset += Utils.getSizeBytesLen(inputArray, offset); // V
	 * 
	 * Util.arrayCopy(inputArray, offset, keySeed, (short) 0, keySeedLength);
	 * 
	 * break;
	 * 
	 * default: break; } } else if ((byte) inputArray[offset] == (byte) 0x7F &&
	 * (byte) inputArray[(short) (offset + 1)] == (byte) 0x49) {
	 * 
	 * offset += 2; // L offset += Utils.getSizeBytesLen(inputArray, offset); //
	 * V
	 * 
	 * switch ((byte) inputArray[offset]) {
	 * 
	 * case (byte) 0x81:
	 * 
	 * offset++; modulusLength = Utils.getLength(inputArray, offset); offset +=
	 * Utils.getSizeBytesLen(inputArray, offset); // V
	 * 
	 * Util.arrayCopy(inputArray, offset, modulus, (short) 0, modulusLength);
	 * 
	 * break; case (byte) 0x82:
	 * 
	 * offset++; publicKeyExponentLength = Utils.getLength( inputArray, offset);
	 * offset += Utils.getSizeBytesLen(inputArray, offset); // V
	 * 
	 * Util.arrayCopy(inputArray, offset, publicKeyExponent, (short) 0,
	 * publicKeyExponentLength);
	 * 
	 * break;
	 * 
	 * default: break; } }
	 * 
	 * if (modulusLength != 0 && publicKeyExponentLength != 0 &&
	 * privateKeyExponentLength != 0 && this.keySeedLength != 0) { if
	 * (modulusLength == privateKeyExponentLength) {
	 * 
	 * this.isCompleted = true; this.keySize = (short) (modulusLength * 8); } }
	 * } } }
	 * 
	 * // ISOException.throwIt(ISO7816.SW_WRONG_DATA); }
	 */

	private byte findKid(byte[] inputArray, short offset, short len) {

		keyId = -1;
		// short size = 0;
		if (inputArray[offset] == (byte) 0xA8) {
			offset++;
			offset += Utils.getSizeBytesLen(inputArray, offset);

			// key id 0xA8 0x4A 0xB6 0x03 0x84 0x01 0x01 0x7F 0x48 0x42 0x97
			// 0x40 0x2D 0x0A 0xA0 0x19 0xBD 0xB2 0xE3

			if ((byte) inputArray[offset] == (byte) 0xB6) {
				offset++;
				offset += Utils.getSizeBytesLen(inputArray, offset);

				// key id value tag
				if ((byte) inputArray[offset] == (byte) 0x84) {
					offset++; // L
					offset += Utils.getSizeBytesLen(inputArray, offset); // V

					keyId = inputArray[offset];

				}
			}
		}

		if (keyId < 0)
			ISOException.throwIt(ISO7816.SW_WRONG_DATA);

		return keyId;
	}

	private boolean verifyInputDataForFinialization(byte[] inputArray,
			short offset) {

		// short size = 0;
		/*
		 * if (inputArray[offset] == (byte) 0x30) {
		 * 
		 * offset++; offset += Utils.getSizeBytesLen(inputArray, offset);
		 * 
		 * // signature for verification if ((byte) inputArray[offset] == (byte)
		 * 0x86) {
		 */

		offset++;
		presigLen = Utils.getLength(inputArray, offset);

		offset += Utils.getSizeBytesLen(inputArray, offset); // V

		Util.arrayCopy(inputArray, offset, this.preSignature, (short) 0,
				presigLen);

		presigOffset = offset;

		// presigLen = getLength(inputArray, offset);
		offset += presigLen;

		return true;

		/*
		 * // hmac if ((byte) inputArray[offset] == (byte) 0x87) {
		 * 
		 * offset++; // L hmacLen = Utils.getLength(inputArray, offset);
		 * 
		 * offset += Utils.getSizeBytesLen(inputArray, offset); // V
		 * 
		 * // Util.arrayCopy(inputArray, offset, this.currentHMAC, // (short)0,
		 * hmacLen);
		 * 
		 * hmacOffset = offset;
		 * 
		 * return true; }
		 */
		// }
		// }//1
		// return false;
	}

	public void encryptMessage(byte[] buffer, short offset, short messLen) {

		// opakuj w pkcs1encoding 0x01... 0xFF...

		try {
			if (keys[currentKid].getPrivateKey() != null
					&& keys[currentKid].publicKey != null) {

				short pkcsLen = encodePkcs1(buffer, offset, messLen, message); // 0x7F

				paddingTlv.init(this.tagPadding, message, (short) 0, pkcsLen);

				// responseTlv.init(cTag, signatureTlv);

				encrypt(message, (short) 0, pkcsLen, preSignature, (short) 0);

				// cipherRSA2.init(keys[currentKid].getPrivateKey(),
				// Cipher.MODE_ENCRYPT);

				// cipherRSA2.doFinal(buffer, offset, messLen, preSignature,
				// (short) 0);

				pkcsLen = 0x80;

				signatureTlv.init(tagPresignature, this.preSignature,
						(short) 0, pkcsLen);

				// Build tlv response
				// 1. signature
				responseTlv.init(cTag, signatureTlv);

				// 2. padding
				responseTlv.append(paddingTlv);

				// responseTlv.append(signatureTlv);

				keyEvoEng.PerformKeyEvo(preSignature, (short) 0, pkcsLen);

				// 3. random - append to tlv HMAC
				randomTlv.init(tagRandom, keyEvoEng.getRandom(), (short) 0,
						keyEvoEng.rLen);

				responseTlv.append(randomTlv);

				// always commit, just for tests

				keyEvoEng.commitEvolutionStep();
				hasMessageToEncrypt = false;

				keyEvoEng.createHmacWithNewKey(preSignature, (short) 0,
						pss.emLen, currentHMAC, (short) 0);

				// 3. HMAC append to tlv HMAC
				hmacTlv.init(this.tagHmac, currentHMAC, (short) 0,
						keyEvoEng.hmacLen);

				responseTlv.append(hmacTlv);

				resLen = responseTlv.size();
				responseTlv.toBytes(result, (short) 0);

				// resLen = pkcsLen;
				// if (keys[currentKid].isContainerUsed == false)
				// keys[currentKid].isContainerUsed = true;
			} else
				ISOException.throwIt(ISO7816.SW_WRONG_DATA); // Invalid key
		} catch (CryptoException cex) {

			ISOException.throwIt(SW_CRYPTO_EXCEPTION);
		}
	}

	private short encodePkcs1(byte[] input, short offset, short inLen,
			byte[] output) {
		// EB = 00 || 01 || PS || 00 || D

		output[0x00] = 0x00;
		output[0x01] = 0x01;
		short modulusLen = (short) (keys[currentKid].keySize / 8);

		if (inLen > (short) (modulusLen - 8 - 3))
			ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);

		for (short i = 2; i != (short) (modulusLen - inLen - 1); i++) {
			output[i] = (byte) 0xFF;
		}

		output[(short) (modulusLen - inLen - 1)] = 0x00;
		// Util.arrayCopy(input, (short)0x00, output, (short)(modulusLen -
		// offset - inLen), inLen);
		Util.arrayCopy(input, offset, output, (short) (modulusLen - inLen),
				inLen);

		return modulusLen;
	}

	private boolean decodePkcs1(byte[] decrytpedResponse, short inOff, short inLen, byte[] message, short messageOff, short messageLen) {
		
		short modulusLen = (short) (keys[currentKid].keySize / 8);
		
		if (inLen < modulusLen) {
			ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
		}

		byte type = decrytpedResponse[0];
		if (type != 0x00)
			ISOException.throwIt(ISO7816.SW_WRONG_DATA);
		
		type = decrytpedResponse[1];
		if (type != 1 && type != 2)
			ISOException.throwIt((short) (ISO7816.SW_WRONG_DATA + 1));

		short start;

		for (start = 2; start != inLen; start++) {
			byte pad = (byte)decrytpedResponse[start];

			if (pad == 0)
				break;

			if (pad != (byte) 0xff)
				ISOException.throwIt((short) (ISO7816.SW_WRONG_DATA + start + 5));
		}

		start++; 
		if (Util.arrayCompare(message, messageOff, decrytpedResponse, start, messageLen) == 0)
		{
			//ISOException.throwIt((short) (ISO7816.SW_WRONG_DATA + 6));
			return true;
		}
			

		return false;
	}

	private boolean pkcsVerify(byte[] message, short messageLen, byte[] result) {
		// TODO Auto-generated method stub
		return true;
	}
	
	public void setMechanism(byte[] input, short offset, short inputLen) {
		// TODO:
	}

	public void destroyContainer(byte[] temporaryBuffer, short offset, short len) {
		
		if (len != 1)
			ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
		
		/*if (!keyEvoEng.finalized)
			ISOException.throwIt(SW_CARD_BLOCKED);*/
		short kid = temporaryBuffer[offset];
		
		if (kid > keys.length)
			ISOException.throwIt(SW_INVALID_CONTAINER);

		Utils.ClearArray(keys[kid].certificateLabel, keys[kid].certificateLabelLen);
		keys[kid].certificateLabelLen = 0;
		
		Utils.ClearArray(keys[kid].userCertificate, keys[kid].userCertLen);
		keys[kid].userCertLen = 0;
		
		Utils.ClearArray(keys[kid].keySeed, keys[kid].keySeedLength);
		keys[kid].keySeedLength = 0;
		
		Utils.ClearArray(keys[kid].modulus, (short) (keys[kid].keySize / 8));
		keys[kid].keySize = 0;
		
	}

	public void setMechanism(byte b) {
		if (b == (byte)0x01)
			this.mechanism = CKA_RSA_PKCS;
		else if (b == (byte)0x0d)
			this.mechanism = CKA_RSA_PSS;
		else
			ISOException.throwIt(SW_CRYPTO_EXCEPTION);
		
	}

	public void destroyKeyContainer(byte b) {
		
		short kid = b;
		
		//keys[kid].isCompleted = false;
		
		if (kid > keys.length)
			ISOException.throwIt(SW_INVALID_CONTAINER);

		//Utils.ClearArray(keys[kid].certificateLabel, keys[kid].certificateLabelLen);
		//keys[kid].certificateLabelLen = 0;
		
		//Utils.ClearArray(keys[kid].certificateId, keys[kid].certificateIdLen);
		//keys[kid].certificateIdLen = 0;
		
	//	Utils.ClearArray(keys[kid].userCertificate, keys[kid].userCertLen);
	//	keys[kid].userCertLen = 0;
		
	//	Utils.ClearArray(keys[kid].keySeed, keys[kid].keySeedLength);
		//keys[kid].keySeedLength = 0;
		
		//Utils.ClearArray(keys[kid].modulus, (short) (keys[kid].keySize / 8));
		//keys[kid].keySize = 0;
		
		keys[kid].isCompleted = false; 
		
		keys[kid].isContainerUsed = false;
		currentKid = -1;
	}

}